package cmd

import (
	"bkmysql/crypt"
	"bufio"
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	cenv     bool
	host     string
	name     string
	pwd      string
	dbname   string
	flush    bool
	filename string
	docker   string
)

var rootCmd = &cobra.Command{
	Use:   "bkmysql",
	Short: "备份mysql数据库",
	Long:  "备份mysql数据库",
	Run: func(cmd *cobra.Command, args []string) {
		if cenv || !isExist("./.env") {
			generateEnv()
		}
		buckup()
	},
}

var code = crypt.Code{
	Salt: "@bkMySql",
}

func init() {
	rootCmd.Flags().BoolVarP(&cenv, "env", "e", false, "Regenerate env file")
}

func generateEnv() {
	scanStringDefault(&host, "127.0.0.1", "请输入MySql服务器地址,默认127.0.0.1")
	scanStringDefault(&name, "root", "请输入MySql服务器用户名,默认root")
	scanString(&pwd, "请输入Mysql服务器密码", "输入的MySql服务器密码不合法")
	scanString(&dbname, "请输入要备份的数据库名称", "输入的数据库名称不合法")
	scanBoolDefault(&flush, false, "是否生成新的binlog文件，y表示生成，n表示不生成，默认不生成")
	scanStringDefault(&filename, fmt.Sprintf("%s.sql", dbname), "请输入备份生成的sql文件路径和文件名，默认是当前目录，文件名和数据库同名")
	scanStringDefault(&docker, "", "如果您的MySql服务器在docker容器中，请输入容器名称，否则不用输入")

	envContent := fmt.Sprintf("HOST=%s\nUSER=%s\nPASSWORD=%s\nDBNAME=%s\nFLUSH=%v\nFILENAME=%s\nCONTAINER=%s\n\n", host, name, code.Encrypt(pwd), dbname, flush, filename, docker)
	if isExist("./.env") {
		fmt.Println("env文件已经存在，是否要覆盖？y表示覆盖")
		var reWrite string
		fmt.Scanln(&reWrite)
		if reWrite != "y" && reWrite != "Y" {
			fmt.Println("没有重写env文件，以上数据将会丢失")
			return
		}
	}
	file, err := os.OpenFile("./.env", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println("打开env文件出错")
		return

	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	writer.WriteString(envContent)
	writer.Flush()

}

func buckup() {
	viper.SetDefault("HOST", "127.0.0.1")
	viper.SetDefault("USER", "root")
	viper.SetDefault("PASSWORD", "")
	viper.SetDefault("DBNAME", "")
	viper.SetDefault("FLUSH", false)
	viper.SetDefault("FILENAME", "")
	viper.SetDefault("CONTAINER", "")

	viper.SetConfigFile("./.env")
	err := viper.ReadInConfig()
	if err != nil {
		panic("获取env配置出错")
	}
	db := viper.GetString("DBNAME")
	fname := viper.GetString("FILENAME")
	if db == "" {
		panic("要备份的数据库名称不能为空")
	}
	if fname == "" {
		fname = db + ".sql"
	}
	pwd := viper.GetString("PASSWORD")
	if pwd != "" {
		pwd, err = code.Decrypt(pwd)
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	var args []string
	var cmdName string

	host := "-h" + viper.GetString("HOST")
	user := "-u" + viper.GetString("USER")
	pwd = "-p" + pwd

	var p *exec.Cmd
	container := viper.GetString("CONTAINER")
	if container != "" {
		args = append(args, "exec", container, "mysqldump", host, user, pwd, "--lock-tables")
		cmdName = "docker"
	} else {
		args = append(args, host, user, pwd, "--lock-tables")
		cmdName = "mysqldump"
	}

	if viper.GetBool("FLUSH") {
		args = append(args, "--flush-logs")
	}
	args = append(args, db)
	// fmt.Println(cmdName, args)
	p = exec.Command(cmdName, args...)
	fmt.Println("准备开始备份" + db)
	var out, stderr bytes.Buffer
	p.Stdout = &out
	p.Stderr = &stderr

	err = p.Run()

	if err != nil {
		fmt.Println("stderr", stderr.String())
		return
	}
	ouContent := out.String()
	file, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println("打开输出文件出错")
		return

	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	writer.WriteString(ouContent)
	writer.Flush()
	fmt.Println("done")
}

func scanString(p *string, tip, errTip string) {
	fmt.Println(tip)
	for true {
		fmt.Scanln(p)
		*p = strings.TrimSpace(*p)
		if *p != "" {
			break
		}
		fmt.Println(errTip)
	}
}

func scanStringDefault(p *string, d, tip string) {
	fmt.Println(tip)
	fmt.Scanln(p)
	*p = strings.TrimSpace(*p)
	if *p == "" {
		*p = d
	}
}

func scanBoolDefault(p *bool, d bool, tip string) {
	fmt.Println(tip)
	var tmp string
	fmt.Scanln(&tmp)
	tmp = strings.TrimSpace(tmp)
	*p = false
	if tmp == "" {
		*p = d
	} else {
		if tmp == "y" || tmp == "Y" || tmp == "1" {
			*p = true
		}
	}

}

func isExist(path string) bool {
	_, err := os.Stat(path)
	return err == nil || os.IsExist(err)
}

func Execute() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println(r)
		}
	}()
	rootCmd.Execute()
}
